﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using iTunesLib;
using System.IO;
using iTunesFastForward.LyricsWikiSOAP;
using System.Text.RegularExpressions;
using System.Web;
using System.Runtime.InteropServices;
using System.Net;

namespace iTunesFastForward.Lyrics
{
    public class LyricsManager
    {

        public bool SaveAuto { get; set; }
        public bool SaveToTags { get; set; }
        public bool SaveToFiles { get; set; }
        public string SaveToFilesPath { get; set; }
        public bool ExcludePodcasts { get; set; }

        private List<LyricsProvider> _providers;
        /// <summary>
        /// List of lyrics providers
        /// </summary>
        public List<LyricsProvider> Providers
        {
            get
            {
                if (_providers == null)
                    _providers = GetProviders();
                return _providers;
            }
        }

        /// <summary>
        /// Returns a list of providers
        /// </summary>
        /// <returns></returns>
        private List<LyricsProvider> GetProviders()
        {
            try
            {
                XDocument xdoc = XDocument.Load("LyricsProviders.xml");

                var providers =
                    from p in xdoc.Element("providers").Elements("provider")
                    select new LyricsProvider
                    {
                        Name = p.Element("name") == null ? null : p.Element("name").Value,
                        Url = p.Element("url") == null ? null : p.Element("url").Value,
                        IsFeelingLucky = p.Element("googleIsFeelingLucky") == null ? false : bool.Parse(p.Element("googleIsFeelingLucky").Value),
                        EncodedUrl = p.Element("encodedUrl") == null || p.Element("encodedUrl").Element("url") == null ? null : p.Element("encodedUrl").Element("url").Value,

                        AppendSearchSuggestionUrl = p.Element("encodedUrl") == null || p.Element("encodedUrl").Element("appendSearchSuggestionUrl") == null ? false : bool.Parse(p.Element("encodedUrl").Element("appendSearchSuggestionUrl").Value),
                        ClassName = p.Element("className") == null ? null : p.Element("className").Value,
                        MethodName = p.Element("methodName") == null ? null : p.Element("methodName").Value,
                        ResultsOnLyricsPageRegex =
                            p.Element("regex") == null || p.Element("regex").Element("resultsOnLyricsPage") == null ? null : new Regex(p.Element("regex").Element("resultsOnLyricsPage").Value, RegexOptions.IgnoreCase | RegexOptions.Singleline),
                        FirstLinkOnSearchPageRegex =
                            p.Element("regex") == null || p.Element("regex").Element("firstLinkOnSearchPage") == null ? null : new Regex(p.Element("regex").Element("firstLinkOnSearchPage").Value, RegexOptions.IgnoreCase | RegexOptions.Singleline),
                        Replacements = p.Element("replacements") == null ? null : GetXmlReplacements(p.Element("replacements"))
                    };

                return providers.ToList();
            }
            catch (Exception e)
            {
                throw new ApplicationException("Can't read xml file LyricsProviders.xml. Please make sure the file is availaible and correct. Error code :" + e.Message);
            }

        }

        private string[][] GetXmlReplacements(XElement xElement)
        {
            List<string[]> replacements = new List<string[]>();

            var rep =
                from r in xElement.Elements("replacement")
                select new { Old = r.Attribute("oldStr").Value, New = r.Attribute("newStr").Value };

            foreach (var item in rep)
            {
                if (!string.IsNullOrEmpty(item.Old) && !string.IsNullOrEmpty(item.New))
                    replacements.Add(new string[] { item.Old, item.New });
            }

            if (replacements.Count == 0)
                return null;
            else
                return replacements.ToArray();
        }

        /// <summary>
        /// Saves the lyrics in ID3 tags and/or text file
        /// </summary>
        /// <param name="track"></param>
        /// <param name="force"></param>
        /// <param name="lyrics"></param>
        public void SaveLyrics(IITTrack track, bool force, string lyrics)
        {
            IITFileOrCDTrack filetrack = null;

            if (track is IITFileOrCDTrack)
                filetrack = (IITFileOrCDTrack)track;

            if (SaveToTags && !iTunesActions.Instance.COMCallDisabled)
            {
                if (filetrack != null)
                {
                    if (force || (!force && string.IsNullOrEmpty(filetrack.Lyrics)))
                        SaveLyricsToTrack(filetrack, lyrics);
                }
            }

            if (SaveToFiles)
            {

                if (Directory.Exists(SaveToFilesPath))
                {
                    if (force || (!force && !LyricsFileExists(track)))
                    {
                        try
                        {
                            SaveLyricsToFile(track, lyrics);
                        }
                        catch (Exception)
                        {
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Remove the lyrics for the track, in ID3 tags or/and text files
        /// </summary>
        /// <param name="track"></param>
        public void RemoveLyrics(IITTrack track)
        {
            Log.Instance.WriteLine(string.Format("[Lyrics] Removing lyrics for track {0} - {1}", track.Artist ?? "Unknown Artist", track.Name ?? "Unknown track"));
            IITFileOrCDTrack filetrack = null;

            if (track is IITFileOrCDTrack)
                filetrack = (IITFileOrCDTrack)track;

            if (SaveToTags)
            {
                try
                {
                    if (filetrack != null)
                        filetrack.Lyrics = string.Empty;
                }
                catch (COMException)
                {
                }
            }

            if (SaveToFiles)
            {
                if (Directory.Exists(SaveToFilesPath))
                {
                    DirectoryInfo info = new DirectoryInfo(SaveToFilesPath);
                    string[] pathes = GetLyricsFilePathes(track);
                    try
                    {
                        if (Directory.Exists(pathes[0]))
                        {
                            if (Directory.Exists(pathes[1]))
                            {
                                File.Delete(pathes[2]);

                                if (info.FullName != new FileInfo(pathes[1]).FullName && Directory.GetFiles(pathes[1]).Length == 0)
                                    Directory.Delete(pathes[1], true);
                            }

                            if (info.FullName != new FileInfo(pathes[0]).FullName && Directory.GetFiles(pathes[0]).Length == 0)
                                Directory.Delete(pathes[0], true);
                        }

                    }
                    catch (Exception)
                    {
                    }
                }
            }
        }

        /// <summary>
        /// Downloads the lyrics and save them in ID3 tags or text files
        /// </summary>
        /// <param name="track"></param>
        /// <param name="force"></param>
        /// <param name="p"></param>
        /// <returns></returns>
        public LyricsDesignation DownloadAndSaveLyrics(IITTrack track, bool force, LyricsProvider p)
        {
            LyricsDesignation lyrics = null;

            // suivant force, tester s'il est nécessaire de télécharger quelque chose
            if (!force)
            {
                if (
                        (SaveToTags && track is IITFileOrCDTrack && string.IsNullOrEmpty(((IITFileOrCDTrack)track).Lyrics))
                        || (SaveToFiles && Directory.Exists(SaveToFilesPath) && !LyricsFileExists(track))
                    )
                    lyrics = FetchLyrics(track, p);
                else
                    lyrics = GetLyrics(track);
            }
            else
                lyrics = FetchLyrics(track, p);

            if (lyrics != null && lyrics.Lyrics != null)
                SaveLyrics(track, force, lyrics.Lyrics);

            return lyrics;

        }

        /// <summary>
        /// Downloads the lyrics depending on the LyricsProviders.xml file
        /// </summary>
        /// <param name="track"></param>
        /// <param name="p"></param>
        /// <returns></returns>
        public LyricsDesignation FetchLyrics(IITTrack track, LyricsProvider p)
        {
            // ne pas chercher de lyrics s'il n'y a pas d'artiste ou de track name
            if (string.IsNullOrEmpty(track.Artist) || string.IsNullOrEmpty(track.Name))
                return null;

            if (p == null)
            {

                // parcourir les Providers un a un et essayer de télécharger
                foreach (var pro in Providers)
                {
                    LyricsDesignation r = FetchLyrics(track, pro);
                    if (r != null)
                        return r;
                }
                return null;
            }

            else
            {

                if (!string.IsNullOrEmpty(p.EncodedUrl))
                {
                    // c'est un parsing d'url
                    string lyricsUrl = null;

                    if (p.FirstLinkOnSearchPageRegex != null)
                    {
                        // s'il y a une premiere page
                        // il doit y avoir au moins cette page là, accessible directement
                        string url = p.EncodedUrl
                            .Replace("{a}", HttpUtility.UrlEncode(ApplyReplacements(p, track.Artist)))
                            .Replace("{n}", HttpUtility.UrlEncode(ApplyReplacements(p, track.Name)));

                        string resp = Utils.SubmitRequestAndGetResponse(url, null, false);

                        // parser le résultat
                        Match m = p.FirstLinkOnSearchPageRegex.Match(resp);

                        if (m.Groups["url"].Success && !string.IsNullOrEmpty(m.Groups["url"].Value))
                        {
                            // récupérer l'url
                           lyricsUrl = p.AppendSearchSuggestionUrl ? p.Url + m.Groups["url"].Value : m.Groups["url"].Value;

                        }
                        else
                            return null;

                    }

                    if (p.ResultsOnLyricsPageRegex != null)
                    {
                        // s'il y avait déjà une page de recherche, on va simplement récupérer les lyrics dessus
                        string result;
                        if (lyricsUrl != null)
                            result = Utils.SubmitRequestAndGetResponse(lyricsUrl, null, false);
                        else
                        {
                            // il doit y avoir au moins cette page là, accessible directement
                            string url =
                                p.EncodedUrl
                                .Replace("{a}", HttpUtility.UrlEncode(ApplyReplacements(p, track.Artist)))
                                .Replace("{n}", HttpUtility.UrlEncode(ApplyReplacements(p, track.Name)));

                            if (p.IsFeelingLucky)
                                result = Utils.GoogleFeelingLuckyRequest(
                                    p.EncodedUrl
                                    .Replace(' ', '+')
                                    .Replace("{a}", HttpUtility.UrlEncode(ApplyReplacements(p, track.Artist)))
                                    .Replace("{n}", HttpUtility.UrlEncode(ApplyReplacements(p, track.Name))));
                            else
                                result = Utils.SubmitRequestAndGetResponse(url, null, false);
                        }

                        if (result == null)
                            return null;

                        // parser le résultat
                        Match m = p.ResultsOnLyricsPageRegex.Match(result);

                        LyricsDesignation d = new LyricsDesignation();
                        if (m.Groups["artist"].Success)
                            d.Artist = HttpUtility.HtmlDecode(m.Groups["artist"].Value);
                        if (m.Groups["song"].Success)
                            d.Song = HttpUtility.HtmlDecode(m.Groups["song"].Value);
                        if (m.Groups["lyrics"].Success)
                            d.Lyrics = HttpUtility.HtmlDecode(m.Groups["lyrics"].Value);

                        if (string.IsNullOrEmpty(d.Lyrics))
                            return null;

                        return d;

                    }
                    else
                        return null;
                }
                else if (!string.IsNullOrEmpty(p.ClassName) && !string.IsNullOrEmpty(p.MethodName))
                {

                    // c'est l'utilisation d'une classe
                    // par réflexion, instancier la classe
                    Type c = Type.GetType(p.ClassName);
                    object instance = Activator.CreateInstance(c);
                    LyricsDesignation d = (LyricsDesignation)c.GetMethod(p.MethodName).Invoke(instance, new object[] { track.Artist, track.Name });

                    return d;
                }
                else
                    return null;
            }
        }

        private string ApplyReplacements(LyricsProvider p, string value)
        {
            if (p.Replacements == null)
                return value;

            foreach (var r in p.Replacements)
                value = value.Replace(r[0], r[1]);
            return value;
        }

        /// <summary>
        /// Returns lyrics from ID3 tags or text file
        /// </summary>
        /// <param name="track"></param>
        /// <returns></returns>
        public LyricsDesignation GetLyrics(IITTrack track)
        {
            if (SaveToTags)
            {
                if (track is IITFileOrCDTrack)
                {
                    IITFileOrCDTrack filetrack = (IITFileOrCDTrack)track;
                    if (!string.IsNullOrEmpty(filetrack.Lyrics))
                        return new LyricsDesignation { Artist = track.Artist, Song = track.Name, Lyrics = filetrack.Lyrics };
                }
            }

            if (SaveToFiles)
            {
                if (File.Exists(GetLyricsFilePathes(track)[2]))
                    return new LyricsDesignation { Artist = track.Artist, Song = track.Name, Lyrics = File.ReadAllText(GetLyricsFilePathes(track)[2]) };
            }

            return null;

        }

        /// <summary>
        /// Checks if the track has lyrics, as text file or in ID3 tags
        /// </summary>
        /// <param name="track"></param>
        /// <returns></returns>
        public bool LyricsExist(IITFileOrCDTrack track)
        {
            if (SaveToTags)
            {
                if (!string.IsNullOrEmpty(track.Lyrics))
                    return true;
            }

            if (SaveToFiles)
            {
                if (File.Exists(GetLyricsFilePathes(track)[2]))
                    return true;
            }

            return false;
        }

        void SaveLyricsToTrack(IITFileOrCDTrack track, string lyrics)
        {
            track.Lyrics = lyrics;
        }

        bool LyricsFileExists(IITTrack track)
        {
            return File.Exists(GetLyricsFilePathes(track)[2]);
        }

        void SaveLyricsToFile(IITTrack track, string lyrics)
        {
            string[] pathes = GetLyricsFilePathes(track);
            string artistDirName = pathes[0];
            string albumDirName = pathes[1];
            string trackFileName = pathes[2];

            if (!Directory.Exists(artistDirName))
                Directory.CreateDirectory(artistDirName);
            if (!Directory.Exists(albumDirName))
                Directory.CreateDirectory(albumDirName);

            File.Delete(trackFileName);
            StreamWriter sw = File.CreateText(trackFileName);
            sw.Write(lyrics);
            sw.Close();
        }

        /// <summary>
        /// Get path for differents folders and txt file
        /// </summary>
        /// <param name="track"></param>
        /// <returns>artist dir, album dir, filename</returns>
        private string[] GetLyricsFilePathes(IITTrack track)
        {
            string artistDirName = track.Artist == null ? SaveToFilesPath : Path.Combine(SaveToFilesPath, EncodePath(track.Artist));
            string albumDirName = track.Album == null ? SaveToFilesPath : Path.Combine(artistDirName, EncodePath(track.Album));
            string trackFileName = track.Name == null ? "Unknown.txt" : Path.Combine(albumDirName, EncodeFileName(track.Name) + ".txt");
            return new string[] { artistDirName, albumDirName, trackFileName };
        }

        /// <summary>
        /// Removes invalid characters from filename
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        string EncodeFileName(string fileName)
        {
            char[] invalidChars = Path.GetInvalidFileNameChars();
            foreach (char c in invalidChars)
                fileName = fileName.Replace(c, '_');

            return fileName;
        }

        /// <summary>
        /// Removes invalid characters from folder path
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        string EncodePath(string path)
        {
            List<char> invalidChars = Path.GetInvalidPathChars().ToList();
            invalidChars.AddRange(new char[] {':', '/', '\\', '*', '?', '"', '<', '>', '|'});

            foreach (char c in invalidChars)
                path = path.Replace(c, '_');

            return path;
        }
    }
}
